#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>

/* BMP 文件头数据结构 */
typedef struct {
    unsigned char type[2];  // 文件类型
    unsigned int size;      // 文件大小
    unsigned short reserved1;   // 保留字段1
    unsigned short reserved2;   // 保留字段2
    unsigned int offset;        // 位图数据偏移量
}__attribute__((packed)) bmp_file_header;

/* 位图信息头数据结构 */
typedef struct {
    unsigned int size;      // 位图头文件信息大小
    int width;              // 图像数据宽度
    int height;             // 图像数据长度
    unsigned short planes;  // 位面数
    unsigned short bpp;     // 像素深度
    unsigned int compression;   // 压缩方式
    unsigned int image_size;    // 图像大小
    int x_pels_per_meter;       // 像素/米
    int y_pels_per_meter;       // 像素/米
    unsigned int clr_used;
    unsigned int clr_omportant;
}__attribute__((packed)) bmp_info_header;



/* 静态全局变量 */
static int width;
static int height;
static unsigned short *screen_base = NULL;
static unsigned long line_length;       // 屏幕  一行的字节数

/********************************************************************
* 函数名称： show_bmp_image
* 功能描述： 在 LCD 上显示指定的 BMP 图片
* 输入参数： 文件路径
* 返 回 值： 成功返回 0, 失败返回-1
********************************************************************/
static int show_bmp_image(const char *path)
{
    bmp_file_header file_h;
    bmp_info_header info_h;
    unsigned short *line_buf = NULL;
    unsigned long line_bytes;
    unsigned int min_h, min_bytes;
    int fd = -1;
    int j;
    int len = 0;

    /* 打开图像 */
    if ((fd = open(path, O_RDONLY)) < 0) {
        perror("open error");
        return -1;
    }

    /* 读取BMP文件头数据 */
    len = sizeof(bmp_file_header);
    if (read(fd, &file_h, len) != len) {
        perror("read error");
        close(fd);
        return -1;
    }

    if (memcmp(file_h.type, "BM", 2)) {
        fprintf(stderr, "it's not a BMP file \n");
        close(fd);
        return -1;
    }

    /* 读取位图数据 */
    len = sizeof(bmp_info_header);
    if (read(fd, &info_h, len) != len) {
        perror("read error");
        close(fd);
        return -1;
    }

    printf("文件大小：%d\n"
           "位图数据偏移量：%d\n"
           "位图头信息大小：%d \n"
           "图像分辨率：%d*%d \n"
           "像素深度：%d\n",
           file_h.size, file_h.offset,
           info_h.size,info_h.width, info_h.height,
           info_h.bpp);

    if (lseek(fd, file_h.offset, SEEK_SET) == -1) {     // 从头开始偏移
        perror("lseek error");
        close(fd);
        return -1;
    }

    line_bytes = info_h.width * info_h.bpp/8;   // 每行字节数
    line_buf = malloc(line_bytes);          // 申请内存
    if (line_buf == NULL) {
        fprintf(stderr, "malloc error\n");
        close(fd);
        return -1;
    }

    if (line_length > line_bytes) {     // 一行屏幕字节数 > 一行图片字节数
        min_bytes = line_bytes;         // 最小字节数是图片一行的字节数
    } else {                            // 否则
        min_bytes = line_length;        // 最小字节数是屏幕 一行的字节数
    }

    if (info_h.height > 0) {        // 倒相位图
        if (info_h.height > height) {      // 图片高度 > 屏幕高度(图片比屏幕大)
            min_h = height;                 // 最小高度为屏幕高度
            lseek(fd, (info_h.height - height)*line_bytes, SEEK_CUR);   // 图片数据偏移
            screen_base += width*(height -1);   // 定位到屏幕左下角
        } else {    // 图片高度小于等于屏幕高度(图片比屏幕小或等于屏幕)
            min_h = info_h.height;      // 最小高度是图片高度
            screen_base += width*(info_h.height - 1);   // 图片从左上角开始显示，定位图片最后一样位置
        }

        for (j=min_h; j>0; screen_base -= width,j--) {
            read(fd, line_buf, line_bytes);             // 读取一行数据
            memcpy(screen_base, line_buf, min_bytes);   // 拷贝到显示buff
        }
    } else {        // 正相位图
        int temp = 0 - info_h.height;
        if (temp > height) {
            min_h = height;
        } else {
            min_h = temp;
        }
        
        for (j=0; j<min_h; j++,screen_base += width) {
            read(fd, line_buf, line_bytes);
            memcpy(screen_base, line_buf, min_bytes);
        }
    }

    close(fd);
    free(line_buf);

    return 0;
}


int main(int argc, char *argv[]) 
{
    struct fb_fix_screeninfo fb_fix;
    struct fb_var_screeninfo fb_var;
    unsigned int screen_size;
    int fd;

    if (argc != 2) {
        fprintf(stderr, "usage:%s<bmp_file>\n", argv[0]);
        exit(-1);
    }

    if ((fd = open("/dev/fb0", O_RDWR)) < 0) {     // 这个地方的 fd=open必须要用括号括起来，否则下面mmp的时候会报错找不到设备
        perror("open error");
        exit(-1);
    }
    
    /* 读取固定参数和可变参数 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);

    /* 计算变量 */
    screen_size = fb_fix.line_length * fb_var.yres;
    line_length = fb_fix.line_length;
    width = fb_var.xres;
    height = fb_var.yres;

    /* 内存映射 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == (void *) screen_base) {
        perror("mmap error");
        close(fd);
        exit(-1);
    }

    /* 向内存中填入默认数据 */
    memset(screen_base, 0xff, screen_size);
    show_bmp_image(argv[1]);    // 显示图片

    /* 释放内存映射 */
    munmap(screen_base, screen_size);
    close(fd);
    exit(EXIT_SUCCESS);
}

